#!/usr/bin/env bash

#------------------------------------------------------------------------------
# Author E-Mail     - terminalforlife@yahoo.com
# Author GitHub     - https://github.com/terminalforlife
#------------------------------------------------------------------------------

Progrm=${0##*/}

Usage(){
	while read; do
		printf '%s\n' "$REPLY"
	done <<-EOF
		Usage: $Progrm [OPTS] [DIR_1 [DIR_2 ...]]

		  -h, --help               - Display this help information.
		  -C, --no-color           - Do not use ANSI color escape sequences.
		  -D, --no-subdirs         - Ignore the 'sheets/_*' subdirectories.
		  -I, --no-lenchk-line     - Ignore the special 'lenchk=.*' line.
		  -N, --no-preview         - Omit the preview of each triggered line.
		  -P, --no-pager           - Do not use less(1) or more(1) for paging.
		  -S, --no-summary         - Omit the summary before $Progrm exits.
		  -W, --no-whilelist       - Do not use the whitelist file.
		  -l, --limit [INT]        - Override the limit of 80 columns.
		  -w, --wl-file [FILE]     - Use an alternative whitelist file.

		  Additional directories can be appended to the argument string in
		  order to process directories otherwise not handled by $Progrm. You
		  can use '--' to let $Progrm know when to stop looking for flags.

		  Files to whitelist must be placed line-by-line; one path per line.

		    cheat.sheets/sheets/_perl/1line
		    cheat.sheets/sheets/find

		  The file paths to provide must begin with 'cheat.sheets/' to indicate
		  the root of the repository, otherwise this feature will fail.

		  The location of the whitelisting file ('$Progrm-excludes') must
		  remain in the same directory in which $Progrm is stored, unless
		  otherwise specified.

		  Alternatively, a file can be whitelisted by ensuring the very first
		  line contains only 'lenchk=disable', akin to a vim(1) modeline.
	EOF
}

Err(){
	printf 'ERROR: %s\n' "$2" 1>&2
	[ $1 -gt 0 ] && exit $1
}

WLFile='lenchk-excludes'
MaxCols=80

while [ "$1" ]; do
	case $1 in
		--)
			break ;;
		--help|-h|-\?)
			Usage; exit 0 ;;
		--limit|-l)
			shift; MaxCols=$1

			if ! [[ $MaxCols =~ ^[0-9]+$ ]]; then
				Err 1 'Invalid column maximum provided.'
			fi ;;
		--no-pager|-P)
			NoPager='True' ;;
		--no-preview|-N)
			NoPreview='True' ;;
		--no-color|-C)
			NoColor='True' ;;
		--no-summary|-S)
			NoSummary='True' ;;
		--no-subdirs|-D)
			NoSubDirs='True' ;;
		--no-whitelist|-W)
			NoWhiteList='True' ;;
		-I|--no-lenchk-line)
			NoLenChkLine='True' ;;
		--wl-file|-w)
			shift

			if [ -z "$1" ]; then
				Err 1 'No alternative whitelist file provided.'
			else
				WLFile=$1
			fi ;;
		-*)
			Err 1 'Incorrect option(s) specified.' ;;
		*)
			break ;;
	esac
	shift
done

# Thank you, Chubin. Feature apparently added in version 3.0 alpha, so should
# be OK, but if anyone is having issues, just `cd` to the 'tests' directory.
[ ${BASH_VERSINFO[0]:-0} -eq 3 ] || cd "${BASH_SOURCE[0]%/*}" &> /dev/null

# Confirm we are in the right place.
git rev-parse --is-inside-work-tree 1> /dev/null 2>&1 ||
	Err 0 'Not inside a Git repository.'

case $PWD in
	*/cheat.sheets/tests)
		;;
	'')
		Err 1 'Unable to determine the CWD.' ;;
	*)
		Err 1 "Not within the 'cheat.sheets/tests' directory." ;;
esac

Dirs=(../sheets/*)
[ "$NoSubDirs" == 'True' ] || Dirs+=(../sheets/*/*)

# Add user's own directories to check, if they exist.
for ArgDir in "$@"; {
	if [ -d "$ArgDir" ]; then
		Dirs+=($ArgDir/*)
	else
		Err 1 "Directory '$ArgDir' not found."
	fi
}

# If the whitelist file exists, use it, unless whitelisting is disabled.
# Keeping this test outside of the loop to avoid unnecessary processing.
if [ "$NoWhiteList" != 'True' ] && [ -f "$WLFile" ]; then
	# Read the whitelist file, line-by-line, generating an array thereof.
	Whitelisted=()
	while read; do
		Whitelisted+=("../${REPLY#cheat.sheets/}")
	done < "$WLFile"
fi

# Using tput(1) for portability.
if type -fP tput &> /dev/null; then
	C_Ellipses=`tput dim; tput setaf 7`
	C_LineNums=`tput bold; tput setaf 2`
	C_FileNames=`tput bold; tput setaf 1`
	C_Reset=`tput sgr0`
else
	Err 1 'Unable to colorize output -- tput(1) not found.'
fi

Main(){
	for File in "${Dirs[@]}"; {
		[ -f "$File" ] || continue

		# Per chubin's desire to have an "in-bound flag"; see #134.
		if [ "$NoLenChkLine" != 'True' ]; then
			SkipFile='False'

			readarray -t Buffer < "$File"
			for BufferLine in "${Buffer[@]}"; {
				if [[ $BufferLine == '# cheat.sh: '* ]]; then
					CheatLine=${BufferLine#'# cheat.sh: '}
					IFS=',' read -a Fields <<< "$CheatLine"
					for Field in "${Fields[@]}"; {
						[ "$Field" == "$Progrm=disable" ] && SkipFile='True'
					}
				fi
			}

			[ "$SkipFile" == 'True' ] && continue
		fi

		# If the current file matches one which is whitelisted, skip it.
		for CurWL in "${Whitelisted[@]}"; {
			[ "$File" == "$CurWL" ] && continue 2
		}

		LineNum=0
		HaveBeenHit='False'
		while read; do
			let LineNum++

			# Ignore non-comment lines for '#' and '//'.
			case $REPLY in
				'#'*|'//'*)
					;;
				*)
					continue ;;
			esac

			if [ ${#REPLY} -gt 80 ]; then
				# We only need to be informed of a hit just the once, per file.
				if [ "$HaveBeenHit" == 'False' ]; then
					# The leading newline character, if needed.
					[ "$NoPreview" == 'True' ] || printf '\n'

					# The filename containing problematic line lengths.
					[ "$NoColor" == 'True' ] || printf "$C_FileNames"
					printf '%s\n' "${File#../}"
					[ "$NoColor" == 'True' ] || printf "$C_Reset"

					HaveBeenHit='True'
					let Hits++
				fi

				if ! [ "$NoPreview" == 'True' ]; then
					# The line number of the problematic length.
					[ "$NoColor" == 'True' ] || printf "$C_LineNums"
					printf '  %7d  ' $LineNum # <-- allows for 9,999,999 lines.
					[ "$NoColor" == 'True' ] || printf "$C_Reset"

					# Cannot make this 80 columns long, due to the indentation
					# and padding, but if you need to test this, for the sake
					# of checking lenchk is doing its job, set the 70 to 80, -
					# then make sure the terminal window is wide enough.
					printf '%s' "${REPLY:0:70}"

					# Cut-off ellipses.
					[ "$NoColor" == 'True' ] || printf "$C_Ellipses"
					printf '...\n'
					[ "$NoColor" == 'True' ] || printf "$C_Reset"
				fi
			fi
		done < "$File"
	}

	if [ ${Hits:-0} -gt 0 -a "$NoSummary" != 'True' ]; then
		printf '\nFound %d file(s) with comment length >%d.\n'\
			$Hits $MaxCols 1>&2
	fi
}

if [ -t 1 -a "$NoPager" != 'True' ]; then
	# Prefer less(1), but have more(1) as a fallback.
	if type -fP less &> /dev/null; then
		Pager='less -R'
	elif type -fP more &> /dev/null; then
		Pager='more'

		if [ "$NoColor" != 'True' ]; then
			Err 1 'Only more(1) is available -- colors unsupported.'
		fi
	else
		Err 1 'Neither less(1) nor more(1) were found.'
	fi

	# Redirecting STDERR to address less(1) bug causing summary to display
	# where it shouldn't; only seems to happen when colorization is enabled.
	Main 2>&1 | $Pager
else
	Main

	# Testing line for use when checking the summary lines up with wc(1). When
	# using this, be sure previewing is disabled and the summary is enabled.
	#printf '%d\n' $((`Main |& wc -l` - 2))
fi
